'use strict'

/**
 * <--    还原前    -->
var p = 10;

function xadd(a,b){
    return p+a+b;
}

function add(a,b)
{
  return a+b;
}

s = add(1,2) + add(111,222);

 * 
 * <--    特点    -->
 * 纯函数：无论这个函数声明在哪个位置，无论在何处调用，只要input(arguments)是确定的，那么output(return)也是确定的，并且不会对输入args及外部变量造成副作用；
 * 见上，xadd依赖外部p，如果xadd声明在别处，计算就会有问题，所以不是纯函数；
 * add就是纯函数；
 * 所以在调用纯函数的地方，如果参数是字面量，那么都可以直接计算出结果，替换后便于阅读
 * 
 * <--    解决办法    -->
 * 先决条件，先要主动提供纯函数，并不是去搜索判断纯函数
 * 还是可以考虑立即调用函数表达式iife，将纯函数和调用都包装到函数体中，直接利用path.evaluate()
 * 为什么不用eval()呢？eval连mdn都不推荐，坑多不好用，效率还低
 * 
 * <--    冗余判断    -->
 * 判断该纯函数的绑定有无被修改过
 * 
 * <--    还原失败（利用iife，准备后续用path.evaluate()，失败）    -->
 * 
var p = 10;

function xadd(a, b) {
  return p + a + b;
}

function add(a, b) {
  return a + b;
}

let s = (0, function () {
  function __add__(a, b) {
    return a + b;
  }

  return __add__(1, 2);
})() + (0, function () {
  function __add__(a, b) {
    return a + b;
  }

  return __add__(111, 222);
})();
 * 
 * <--    新解决办法    -->
 * 利用解释执行的思路，用FunctionDeclaration解释构建Function对象
 * 凡是引用到该函数的path，拆去出args，利用Function.prototype.apply(null, args)解释计算出结果
 * 引用处的CallExpression替换字面量值
 * 
 * <--    还原成功    -->
var p = 10;

function xadd(a, b) {
  return p + a + b;
}

function add(a, b) {
  return a + b;
}

let s = 3 + 333;
 * 
 **/

const {parse} = require('@babel/parser');
const traverse = require('@babel/traverse').default;
const t = require('@babel/types');
const gernerator = require('@babel/generator').default;

let code = `var p = 10;

function xadd(a,b){
    return p+a+b;
}

function add(a,b)
{
  return a+b;
}

let s = add(1,2) + add(111,222);`

const visitor = {
    FunctionDeclaration:{
        enter:function(path){
            let pureName = 'add';
            if(path.node.id.name != pureName){return}
            let binding = path.scope.parent.getBinding(path.node.id.name);
            
            // 如果函数被修改过，或者没有被引用过，直接返回
            if(!binding || !binding.constant || !binding.referenced){return}

            /* 由于path.evalutate()不支持iife，失败的解决办法
            // 构建立即调用函数表达式模板
            let iifeModeNode = t.callExpression(
                t.sequenceExpression(
                    [t.numericLiteral(0),
                    t.functionExpression(null, [],
                        t.blockStatement([
                            parse(path.toString()).program.body[0],    // 深拷贝
                            t.returnStatement()
                    ]))]),
                [])
            
            // 提前保存引用path，因为后续创建时要不断新建纯函数调用，同时babel是响应式，可以规避无限循环
            let callPaths = [];
            for (let refPath of binding.referencePaths){
                if(t.isCallExpression(refPath.parentPath.node)){
                    callPaths.push(refPath.parentPath);
                }
            }
            for(let callPath of callPaths){
                // console.log(callPath.toString())
                let iifeNode = parse(gernerator(iifeModeNode).code).program.body[0].expression;    // 深拷贝
                // console.log(iifeNode);
                iifeNode.callee.expressions[1].body.body[1] = t.returnStatement(callPath.node)
                let __pureName__ = '__'+pureName+'__';
                iifeNode.callee.expressions[1].body.body[0].id.name = __pureName__;
                iifeNode.callee.expressions[1].body.body[1].argument.callee.name = __pureName__;
                let wrapPath = callPath.replaceInline(iifeNode)[0];
                // console.log(wrapPath.toString());
                let {confident, value} = wrapPath.evaluate();    // 具体失败点，path.evaluate()  path只支持identifier || memberExpression，不支持iife
                if(confident){
                    wrapPath.replaceInline(t.valueToNode(value));
                }
            }
            */

            // （解释）构建纯函数对象Function
            let params = [], blockStmts = path.get('body.body').toString();
            for(let {name} of path.node.params){
                params.push(name);
            }
            let func = new Function(params, blockStmts);
            // console.log(func.toString());

            for(let refPath of binding.referencePaths){
                if(!t.isCallExpression(refPath.parentPath.node)){continue}
                // 构建实参数组
                let argPaths = refPath.parentPath.get('arguments'), args = [];
                for(let argPath of argPaths){
                    if(t.isUnaryExpression(argPath.node)){
                        let {confident, value} = argPath.evaluate();
                        if(confident){
                            args.push(value);
                        }else{
                            args = [];
                            break
                        }
                    }else if(t.isLiteral(argPath.node)){
                        args.push(argPath.node.value);
                    }else{
                        args = [];
                        break
                    }
                }
                if(args.length){
                    // console.log(args);
                    // 解释计算求值
                    let value = func.apply(null, args);
                    refPath.parentPath.replaceInline(t.valueToNode(value));
                }
            }
        },
    },
}

let ast = parse(code);
traverse(ast, visitor);
console.log(gernerator(ast).code);

// let func = new Function(['a','b'], `return a+b`);
// func.name = 'add';

// console.log(func.toString(), func.name);

